﻿using System;
using System.Collections.ObjectModel;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using ClassRoom.Packets;
using System.IO;
using System.Xml.Serialization;
using ClassRoom.Commands;
using ClassRoom.Views;
using System.Collections.Generic;
using Newtonsoft.Json;
using System.Text;
using System.Windows.Threading;
using FluxJpeg.Core;
using FluxJpeg.Core.Encoder;
using VoiceEncoder;
using ClassRoom.LNService;

namespace ClassRoom.ViewModels
{
    public class ClassRoomViewModel : ViewModelBase
    {
        #region Fields

        private LNService.LNServiceClient LNService = new LNService.LNServiceClient();
        private ObservableCollection<ChatMessage> _ChatMessages = new ObservableCollection<ChatMessage>();
        private ObservableCollection<UserInfo> _UserInfos = new ObservableCollection<UserInfo>();
        public Network _Network = new Network();
        private string _ChatMessage;
        private UserInfo _UserInfo;
        private ClassRoomView _View;
        private CaptureSource _captureSource;
        private VideoBrush _webcamBrush;
        private BitmapImage _webcamHost;
        private BitmapImage _currentPPTSlide;
        private PcmToWave _pcm = new PcmToWave();
        private MemoryAudioSink _sink;
        private ICommand _SendCommand;
        private ICommand _StartWebcamCommand;
        private DispatcherTimer _timerSendWebcam;
        private int _currentPPTSlideNo;
        private int _totalPPTSlides;
        private bool _IsBusy;
        private bool _EnableSendWCVoice = false;

        #endregion

        #region Constructors

        public ClassRoomViewModel(ClassRoomView view)
        {           
            this.IsBusy = true;
            this._View = view;

            try
            {
                LNService.GetLoginUserCompleted += new EventHandler<LNService.GetLoginUserCompletedEventArgs>(this.LNService_GetLoginUserCompleted);
                LNService.CheckIsCourseOwnerCompleted += new EventHandler<LNService.CheckIsCourseOwnerCompletedEventArgs>(this.LNService_CheckIsCourseOwnerCompleted);

                LNService.GetLoginUserAsync();
            }
            catch
            {
            }
        }

        #endregion

        #region Properties

        public ObservableCollection<ChatMessage> ChatMessages
        {
            get
            {
                return this._ChatMessages;
            }

            set
            {
                this._ChatMessages = value;
                base.RaisePropertyChanged("ChatMessages");
            }
        }

        public ObservableCollection<UserInfo> UserInfos
        {
            get
            {
                return this._UserInfos;
            }

            set
            {
                this._UserInfos = value;
                base.RaisePropertyChanged("UserInfos");
            }
        }

        public string ChatMessage
        {
            get
            {
                return this._ChatMessage;
            }

            set
            {
                this._ChatMessage = value;
                base.RaisePropertyChanged("ChatMessage");
            }
        }

        public VideoBrush WebcamBrush
        {
            get
            {
                return this._webcamBrush;
            }

            set
            {
                this._webcamBrush = value;
                base.RaisePropertyChanged("WebcamBrush");
            }
        }

        public BitmapImage WebcamHost
        {
            get
            {
                return this._webcamHost;
            }

            set
            {
                this._webcamHost = value;
                base.RaisePropertyChanged("WebcamHost");
            }
        }

        public BitmapImage CurrentPPTSlide
        {
            get
            {
                return this._currentPPTSlide;
            }

            set
            {
                this._currentPPTSlide = value;
                base.RaisePropertyChanged("CurrentPPTSlide");
            }
        }

        public ICommand SendCommand
        {
            get
            {
                if (this._SendCommand == null)
                {
                    this._SendCommand = new DelegateCommand<object>(this.ExecuteSendCommand);
                }

                return this._SendCommand;
            }
        }

        public ICommand StartWebcamCommand
        {
            get
            {
                if (this._StartWebcamCommand == null)
                {
                    this._StartWebcamCommand = new DelegateCommand<object>(this.ExecuteStartWebcamCommand);
                }

                return this._StartWebcamCommand;
            }
        }

        public int CurrentPPTSlideNo
        {
            get
            {
                return this._currentPPTSlideNo;
            }

            set
            {
                this._currentPPTSlideNo = value;
                base.RaisePropertyChanged("CurrentPPTSlideNo");
            }
        }

        public int TotalPPTSlides
        {
            get
            {
                return this._totalPPTSlides;
            }

            set
            {
                this._totalPPTSlides = value;
                base.RaisePropertyChanged("TotalPPTSlides");
            }
        }

        public bool IsBusy
        {
            get
            {
                return this._IsBusy;
            }

            set
            {
                this._IsBusy = value;
                base.RaisePropertyChanged("IsBusy");
            }
        }

        #endregion

        #region Methods
        public void InitWebcam()
        {
            // Create the CaptureSource.
            if (_captureSource.State != CaptureState.Started)
            {
                // set the source on the VideoBrush used to display the video
                _webcamBrush.SetSource(_captureSource);

                // Set the Fill property of the Rectangle to the VideoBrush.
                this._View.recWebcam.Fill = _webcamBrush;

                // Request access to device and verify the VideoCaptureDevice is not null.
                if ((CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess()))
                {
                    try
                    {
                        _sink = new MemoryAudioSink();

                        if (_captureSource.AudioCaptureDevice.SupportedFormats.Count >= 10)
                        {
                            AudioFormat audioFormat = _captureSource.AudioCaptureDevice.SupportedFormats[10];
                            _captureSource.AudioCaptureDevice.DesiredFormat = audioFormat;
                        }
                        else MessageBox.Show("Your sound device may do not support (16 bits,8000 samples,1 channel) please try to find this Audio Compression in SupportedFormats collection!");

                        _sink.OnBufferFulfill += new EventHandler(SendVoiceBuffer);

                        _sink.CaptureSource = _captureSource;

                        _captureSource.Start();
                        _sink.StartSending = true;
                    }
                    catch (InvalidOperationException ex)
                    {
                        // Notify user that the webcam could not be started.
                        MessageBox.Show("There was a problem starting the webcam " +
                                        "If using a Mac, verify default device settings." +
                                        "Right click app to access the Configuration settings.");
                    }
                }
                else
                {
                    MessageBox.Show("Could not start Webcam. Verify device is connected " +
                                    "and privacy permission allow access to device.");
                }
            }
        }

        public void ExecuteSendCommand(object parameter)
        {
            if (this.ChatMessage != null && this.ChatMessage.Trim().Length > 0)
            {
                ChatMessage chatMessage = new ChatMessage(this._UserInfo.Username, this.ChatMessage);
                this.ChatMessages.Add(chatMessage);
                this._Network.Send(new ChatMessage(this._UserInfo.Username, this.ChatMessage), ObjectType.ChatMessage);
                this.ChatMessage = "";
                this._View.lbChatMessages.UpdateLayout();
                this._View.lbChatMessages.ScrollIntoView(chatMessage);
            }
        }

        private void ExecuteStartWebcamCommand(object parameter)
        {
            this._EnableSendWCVoice = true;
            InitWebcam();
            this._View.btnStartWebcam.Visibility = Visibility.Collapsed;
            this._timerSendWebcam.Start();            
        }

        public void StopSendWebcam()
        {
            this._EnableSendWCVoice = false;
        }

        public void StartSendWebcam()
        {
            this._EnableSendWCVoice = true;
        }

        #region Encoding/Decoding Webcam Methods
        public static void EncodeJpeg(WriteableBitmap bmp, Stream dstStream)
        {
            // Init buffer in FluxJpeg format
            int w = bmp.PixelWidth;
            int h = bmp.PixelHeight;
            int[] p = bmp.Pixels;
            byte[][,] pixelsForJpeg = new byte[3][,]; // RGB colors
            pixelsForJpeg[0] = new byte[w, h];
            pixelsForJpeg[1] = new byte[w, h];
            pixelsForJpeg[2] = new byte[w, h];

            // Copy WriteableBitmap data into buffer for FluxJpeg
            int i = 0;
            for (int y = 0; y < h; y++)
            {
                for (int x = 0; x < w; x++)
                {
                    int color = p[i++];
                    pixelsForJpeg[0][x, y] = (byte)(color >> 16); // R
                    pixelsForJpeg[1][x, y] = (byte)(color >> 8);  // G
                    pixelsForJpeg[2][x, y] = (byte)(color);       // B
                }
            }
            //Encode Image as JPEG
            var jpegImage = new FluxJpeg.Core.Image(new ColorModel { colorspace = ColorSpace.RGB }, pixelsForJpeg);
            var encoder = new JpegEncoder(jpegImage, 95, dstStream);
            encoder.Encode();
        }
        public static WriteableBitmap DecodeJpeg(Stream srcStream)
        {
            // Decode JPEG
            var decoder = new FluxJpeg.Core.Decoder.JpegDecoder(srcStream);
            var jpegDecoded = decoder.Decode();
            var img = jpegDecoded.Image;
            img.ChangeColorSpace(ColorSpace.RGB);

            // Init Buffer
            int w = img.Width;
            int h = img.Height;
            var result = new WriteableBitmap(w, h);
            int[] p = result.Pixels;
            byte[][,] pixelsFromJpeg = img.Raster;
            // Copy FluxJpeg buffer into WriteableBitmap
            int i = 0;
            for (int x = 0; x < w; x++)
            {
                for (int y = 0; y < h; y++)
                {
                    p[i++] = (0xFF << 24) // A
                                | (pixelsFromJpeg[0][x, y] << 16) // R
                                | (pixelsFromJpeg[1][x, y] << 8)  // G
                                | pixelsFromJpeg[2][x, y];       // B
                }
            }

            return result;
        }

        #endregion Encoding/Decoding Methods

        #region Encoding/Decoding Audio Methods

        private void SendVoiceBuffer(object VoiceBuffer, EventArgs e)
        {
            if (this._EnableSendWCVoice)
            {
                byte[] PCM_Buffer = (byte[])VoiceBuffer;
                byte[] Encoded = VoiceEncoder.G711Audio.ALawEncoder.ALawEncode(PCM_Buffer);

                _Network.Send(new Voice(Encoded), ObjectType.Voice);
            }
        }

        byte[] soundBuffer = new byte[15840];
        int total = 0;

        private void PlayReceivedBuffer(byte[] Encodedbuffer)
        {
            int length = Encodedbuffer.Length;
            if (length > soundBuffer.Length - total)
            {
                length = soundBuffer.Length - total;
            }

            Array.Copy(Encodedbuffer, 0, soundBuffer, total, length);
            total += length;
            if (total == soundBuffer.Length)
            {
                try
                {
                    //Decode to Wave Format Then Play
                    byte[] DecodedBuffer = new byte[soundBuffer.Length * 2];
                    VoiceEncoder.G711Audio.ALawDecoder.ALawDecode(soundBuffer, out DecodedBuffer);
                    total = 0;
                    soundBuffer = new byte[15840];
                    PlayWave(DecodedBuffer);

                }
                catch (Exception) { }
            }
        }

        void PlayWave(byte[] PCMBytes)
        {
            MemoryStream ms_PCM = new MemoryStream(PCMBytes);
            MemoryStream ms_Wave = new MemoryStream();

            _pcm.SavePcmToWav(ms_PCM, ms_Wave, 16, 8000, 1);

            WaveMediaStreamSource WaveStream = new WaveMediaStreamSource(ms_Wave);
            this._View.mediaVoice.SetSource(WaveStream);
            this._View.mediaVoice.Play();
        }

        #endregion

        #endregion

        #region Event Handles

        private void TimerSendWebcam_Tick(object sender, EventArgs e)
        {
            if (this._EnableSendWCVoice)
            {
                WriteableBitmap bmp = new WriteableBitmap(this._View.recWebcam, null);
                MemoryStream ms = new MemoryStream();
                EncodeJpeg(bmp, ms);
                _Network.Send(new Webcam(ms.ToArray()), ObjectType.Webcam);
            }
        }
        #endregion

        #region Network Event Handles

        private void _Network_OnConnectCompleted(object sender, System.Net.Sockets.SocketAsyncEventArgs e)
        {
            // Send ClassRoomID
            try
            {
                this._Network.Send(this._UserInfo, ObjectType.UserInfo);
            }
            catch { }
        }

        private void _Network_OnReceiveCompleted(byte[] message)
        {
            try
            {
                lock (message)
                {
                    // Just checking user is still online or not
                    if (message.Length == 1)
                    {
                        return;
                    }

                    byte[] dataType = new byte[4];
                    Array.Copy(message, 0, dataType, 0, 4);
                    ObjectType objType = (ObjectType)BitConverter.ToInt32(dataType, 0);
                    string input = Encoding.UTF8.GetString(message, 4, message.Length - 4);
                    switch (objType)
                    {
                        case ObjectType.UserInfo:
                            UserInfo userInfo = JsonConvert.DeserializeObject<UserInfo>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                this._UserInfos.Add(userInfo);
                            });

                            break;
                        case ObjectType.UserInRoom:
                            List<UserInfo> userInRoom = JsonConvert.DeserializeObject<List<UserInfo>>(input);
                            if (userInRoom != null && userInRoom.Count > 0)
                            {
                                Dispatcher.BeginInvoke(delegate()
                                {
                                    foreach (UserInfo uifo in userInRoom)
                                    {
                                        this.UserInfos.Add(uifo);
                                    }
                                });
                            }

                            Dispatcher.BeginInvoke(delegate()
                            {
                                this.IsBusy = false;
                            });

                            break;
                        case ObjectType.DisconnectUser:
                            UserInfo disconnectedUserInfo = JsonConvert.DeserializeObject<UserInfo>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                if (this._UserInfo.Username == disconnectedUserInfo.Username)
                                {
                                    this.IsBusy = true;
                                    MessageBox.Show("Đã bị ngắt kết nối đến máy chủ");
                                }
                                else
                                {
                                    for (int i = 0; i < this.UserInfos.Count; i++)
                                    {
                                        if (this.UserInfos[i].Username == disconnectedUserInfo.Username)
                                        {
                                            this.UserInfos.RemoveAt(i);
                                        }
                                    }
                                }
                            });

                            break;
                        case ObjectType.ChatMessage:
                            ChatMessage chatMessage = JsonConvert.DeserializeObject<ChatMessage>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                this.ChatMessages.Add(chatMessage);
                                this._View.lbChatMessages.UpdateLayout();
                                this._View.lbChatMessages.ScrollIntoView(chatMessage);
                            });

                            break;
                        case ObjectType.Webcam:
                            Webcam webcam = JsonConvert.DeserializeObject<Webcam>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                MemoryStream ms = new MemoryStream(webcam.WebcamHost);
                                BitmapImage bi = new BitmapImage();
                                bi.SetSource(ms);
                                this.WebcamHost = bi;
                            });

                            break;
                        case ObjectType.Voice:
                            Voice voice = JsonConvert.DeserializeObject<Voice>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                PlayReceivedBuffer(voice.VoiceBuffer);
                            });

                            break;
                        case ObjectType.BlackBoard:
                            BlackBoard blackBoard = JsonConvert.DeserializeObject<BlackBoard>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                this._View.ExecuteBlackBoardPacket(blackBoard);
                            });

                            break;
                        case ObjectType.OneSlide:
                            if (!this._EnableSendWCVoice) this._EnableSendWCVoice = true;
                            OneSlide slide = JsonConvert.DeserializeObject<OneSlide>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                MemoryStream ms = new MemoryStream(slide.SlideImageBuffer);
                                BitmapImage bi = new BitmapImage();
                                bi.SetSource(ms);
                                this.CurrentPPTSlide = bi;
                                this.CurrentPPTSlideNo = slide.SlideNo;
                            });

                            break;
                        case ObjectType.PPTSlidesInfo:
                            PPTSlidesInfo pptSlidesInfo = JsonConvert.DeserializeObject<PPTSlidesInfo>(input);
                            Dispatcher.BeginInvoke(delegate()
                            {
                                this.CurrentPPTSlideNo = pptSlidesInfo.CurPPTSlide;
                                this.TotalPPTSlides = pptSlidesInfo.TotalPPTSlide;
                            });

                            break;
                    }
                }
            }
            catch
            { 
            
            }
        }

        #endregion

        #region SERVICE EVENT HANDLES

        private void LNService_GetLoginUserCompleted(object sender, LNService.GetLoginUserCompletedEventArgs e)
        {
            GlobalParameters.LoginUser = e.Result;
            LNService.CheckIsCourseOwnerAsync(GlobalParameters.LoginUser.AccountID_u, GlobalParameters.CourseID);
        }

        private void LNService_CheckIsCourseOwnerCompleted(object sender, CheckIsCourseOwnerCompletedEventArgs e)
        {
            GlobalParameters.IsOwner = e.Result;
            if (e.Result)
            {
                this._View.btnStartWebcam.Visibility = System.Windows.Visibility.Visible;
                this._View.recWebcam.Visibility = System.Windows.Visibility.Visible;
                this._View.imgOpenPPT.Visibility = System.Windows.Visibility.Visible;
                this._View.imgNextPPTSlide.Visibility = System.Windows.Visibility.Visible;
                this._View.imgPrevPPTSlide.Visibility = System.Windows.Visibility.Visible;
            }

            _timerSendWebcam = new DispatcherTimer();
            _timerSendWebcam.Interval = new TimeSpan(0, 0, 0, 0, 40);
            _timerSendWebcam.Tick += new EventHandler(this.TimerSendWebcam_Tick);
            _captureSource = new CaptureSource();

            // Set the video capture source the WebCam.
            _captureSource.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
            _webcamBrush = new VideoBrush();

            this._UserInfo = new UserInfo(GlobalParameters.LoginUser.Nickname_s, GlobalParameters.CourseID, GlobalParameters.IsOwner);
            this.UserInfos.Add(this._UserInfo);

            this._Network.OnConnectCompleted += new Network.dlgConnectCompleted(this._Network_OnConnectCompleted);
            this._Network.OnReceiveCompleted += new Network.dlgReceiveCompleted(this._Network_OnReceiveCompleted);

            this._Network.Connect(GlobalParameters.IPServer);

            GlobalParameters.Network = this._Network;
        }

        #endregion
    }
}
